package com.whosly.mysql.protocol;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 客户端/服务器端的能力
 */
public enum Capability {
    /**
     * new more secure passwords
     * CLIENT_LONG_PASSWORD
     */
    LongPassword("CLIENT_LONG_PASSWORD"),// 0x00000001 --> 1
    /**
     * Found instead of affected rows, 返回找到（匹配）的行数，而不是改变了的行数。
     * CLIENT_FOUND_ROWS
     */
    FoundRows("CLIENT_FOUND_ROWS"),// 0x00000002 --> 2
    /**
     * Get all column flags
     * CLIENT_LONG_FLAG
     */
    LongColumnFlags("CLIENT_LONG_FLAG"),// 0x00000004 --> 4
    /**
     * One can specify db on connect
     * CLIENT_CONNECT_WITH_DB
     */
    ConnectWithDatabase("CLIENT_CONNECT_WITH_DB"),// 0x00000008 --> mask = 1 << ordinal() = 8;
    /**
     * Don't allow database.table.column
     * 不允许“数据库名.表名.列名”这样的语法。这是对于ODBC的设置。
     * 当使用这样的语法时解析器会产生一个错误，这对于一些ODBC的程序限制bug来说是有用的。
     *
     * CLIENT_NO_SCHEMA
     */
    DontAllowDatabaseTableColumn("CLIENT_NO_SCHEMA"),// 0x00000010 --> 16
    /**
     * Can use compression protocol, 使用压缩协议
     * CLIENT_COMPRESS
     */
    CanUseCompressionProtocol("CLIENT_COMPRESS"),// 0x00000020 --> 32
    /**
     * Odbc client
     * CLIENT_ODBC
     */
    OdbcClient("CLIENT_ODBC"),// 0x00000040 --> 64
    /**
     * Can use LOAD DATA LOCAL
     *
     * CLIENT_LOCAL_FILES
     */
    CanUseLoadDataLocal("CLIENT_LOCAL_FILES"),// 0x00000080 --> 128

    /**
     * Ignore spaces before'('
     * 允许在函数名后使用空格。所有函数名可以预留字。
     *
     * CLIENT_IGNORE_SPACE
     */
    IgnoreSpaceBeforeLeftParen("CLIENT_IGNORE_SPACE"),// 0x00000100 --> 256
    /**
     * New 4.1 protocol This is an interactive client
     * CLIENT_PROTOCOL_41
     */
    Speaks4_1Protocl_NewFlag("CLIENT_PROTOCOL_41"),//0x00000200 --> 512
    /**
     * This is an interactive client
     * 允许使用关闭连接之前的不活动交互超时的描述，而不是等待超时秒数。
     * 户端的会话等待超时变量变为交互超时变量。
     *
     * CLIENT_INTERACTIVE
     */
    InteractiveClient("CLIENT_INTERACTIVE"),// 0x00000400 --> 1024
    /**
     * Switch to SSL after handshake
     * 使用SSL。这个设置不应该被应用程序设置，他应该是在客户端库内部是设置的。
     * 可以在调用mysql_real_connect()之前调用mysql_ssl_set()来代替设置。
     *
     * CLIENT_SSL
     */
    SwitchToSslAfterHandshake("CLIENT_SSL"),//0x00000800 --> 2048
    /**
     * IGNORE sigpipes
     * 阻止客户端库安装一个SIGPIPE信号处理器。
     * 这个可以用于当应用程序已经安装该处理器的时候避免与其发生冲突。
     *
     * CLIENT_IGNORE_SIGPIPE
     */
    IgnoreSigpipes("CLIENT_IGNORE_SIGPIPE"),// 0x00001000 --> 4096
    /**
     * Client knows about transactions
     * CLIENT_TRANSACTIONS
     */
    KnowsAboutTransactions("CLIENT_TRANSACTIONS"),// 0x00002000 --> 8192
    /**
     * CLIENT_RESERVED, Old flag for 4.1 protocol
     * CLIENT_RESERVED
     */
    Speaks4_1Protocl_OldFlag("CLIENT_RESERVED"),// 0x00004000 --> 16384
    /**
     * CLIENT_SECURE_CONNECTION, New 4.1 authentication
     * CLIENT_SECURE_CONNECTION
     */
    CanDo4_1Authentication("CLIENT_SECURE_CONNECTION"),//0x00008000 --> 32768

    /**
     * Enable/disable multi-stmt support
     * 通知服务器客户端可以发送多条语句（由分号分隔）。如果该标志为没有被设置，多条语句执行。
     * CLIENT_MULTI_STATEMENTS
     */
    MultipleStatements("CLIENT_MULTI_STATEMENTS"),// 0x00010000 --> 65536

    /**
     * Enable/disable multi-results
     * 通知服务器客户端可以处理由多语句或者存储过程执行生成的多结果集。
     * 当打开CLIENT_MULTI_STATEMENTS时，这个标志自动的被打开。
     *
     * CLIENT_MULTI_RESULTS
     */
    MultipleResults("CLIENT_MULTI_RESULTS"),// 0x00020000 --> 131072

    /**
     * CLIENT_PS_MULTI_RESULTS
     */
    PsMultipleResults("CLIENT_PS_MULTI_RESULTS"),// 0x00040000

    /**
     *
     * CLIENT_PLUGIN_AUTH
     */
    PluginAuth("CLIENT_PLUGIN_AUTH"), // 0x00080000 -->  524288
    /**
     * CLIENT_CONNECT_ATTRS
     */
    ConnectAttrs("CLIENT_CONNECT_ATTRS"), // 0x00100000

    /**
     * CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
     */
    PluginAuthLenEncClientData("CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA"), // 0x00200000
    /**
     * CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS
     */
    ClientCanHandleExpiredPasswords("CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS"),  // 0x00400000
    /**
     * CLIENT_SESSION_TRACK
     */
    SessionVariableTracking("CLIENT_SESSION_TRACK"), // 0x00800000

    /**
     * CLIENT_DEPRECATE_EOF
     */
    DeprecateEof("CLIENT_DEPRECATE_EOF"), // 0x01000000 --> 16777216

    Unused_1("Unused_1"),
    Unused_2("Unused_2"),
    Unused_3("Unused_3"),
    Unused_4("Unused_4"),
    Unused_5("Unused_5"),
    Unused_6("Unused_6"),
    ;

    private final int mask;
    private final String name;

    Capability(String name) {
        this.mask = 1 << ordinal();
        this.name = name;
    }

    public static boolean isEnabled(long features, Capability capability) {
        // features 不为0且 capability.mask 不为0
        return (features & capability.mask) != 0;
    }

    public static long config(long features, Capability capability, boolean state) {
        if (state) {
            features |= capability.mask;
        } else {
            features &= ~capability.mask;
        }

        return features;
    }

    public static Capability fromName(String name) {
        for (Capability capability : values()) {
            if (capability.name.equals(name)) {
                return capability;
            }
        }

        throw new IllegalArgumentException("No ClientCapability found for name: " + name);
    }

    public static int compose(Capability... capabilities) {
        if (capabilities == null) {
            return 0;
        }

        // 去重
        List<Capability> list = Arrays.stream(capabilities).distinct().collect(Collectors.toList());
        int value = 0;

        for (Capability capability : list) {
            /**
             * a |= b 等价于  a = a | b
             * 具体规则为：
             * 两个二进制对应位为0时该位为0，否则为1。
             *
             * 拿5的二进制 0000 0101 和 3的二进制 0000 0011 进行|运算，
             * 后三位的的对应位都不是同时等于0，所以最终结果为 0000 0111 也就是7的二进制。
             *
             * int a = 5; // 0000 0101
             * int b = 3; // 0000 0011
             * a |= b; // 0000 00111
             *
             * a == 7
             */
            value |= capability.mask;
        }

        return value;
    }

    public static List<Capability> describe(long mask) {
        List<Capability> ret = new ArrayList<>();
        for (Capability capability : values()) {
            if (isEnabled(mask, capability)) {
                ret.add(capability);
            }
        }

        return ret;
    }



    /**
     * Get handshake capability flags lower bit.
     *
     * @return handshake capability flags lower bit
     */
    public static int calculateHandshakeCapabilityFlagsLower() {
        return compose(
                LongPassword, FoundRows, LongColumnFlags, ConnectWithDatabase, OdbcClient,
                IgnoreSpaceBeforeLeftParen,
                /* CLIENT_SECURE_CONNECTION */Speaks4_1Protocl_NewFlag,
                InteractiveClient, IgnoreSigpipes,
                CanDo4_1Authentication, MultipleStatements,
                PluginAuth, ConnectAttrs,

                DontAllowDatabaseTableColumn, CanUseCompressionProtocol, CanUseLoadDataLocal,
                Speaks4_1Protocl_OldFlag,
                MultipleResults,
                /* 多结果集 */PsMultipleResults,
                PluginAuthLenEncClientData,
                ClientCanHandleExpiredPasswords,
                SessionVariableTracking
                , DeprecateEof
        );
    }

    /**
     * Get handshake capability flags upper bit.
     *
     * @return handshake capability flags upper bit
     */
    public static int calculateHandshakeCapabilityFlagsUpper() {
        return compose(PluginAuth) >> 16;
    }

    public static boolean isPluginAuthLenencClientData(int value) {
        return (value & PluginAuthLenEncClientData.mask) != 0;
    }

    public static boolean isCanDo41Anthentication(int value) {
        return (value & CanDo4_1Authentication.mask) != 0;
    }
}